﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using BizzCommInterfaces;
using System.IO;
using System.Reflection;
using System.Configuration;
using log4net;
namespace BizzCore
{
    class TraysHandler
    {
        private static readonly ILog auditLog =
                      LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
        private static TraysHandler _instance = null;
        private string inboxesFolder;
        private string outboxesFolder;
        private List<IInbox> inboxProviders;
        private List<IOutbox> outboxProviders;
        private List<IInbox> concreteInboxes;
        private List<IOutbox> concreteOutboxes;

        protected TraysHandler()
        {
            inboxProviders = new List<IInbox>();
            outboxProviders = new List<IOutbox>();
            concreteInboxes = new List<IInbox>();
            concreteOutboxes = new List<IOutbox>();
            inboxesFolder = ConfigurationManager.AppSettings["InboxProviders"].ToString();
            outboxesFolder = ConfigurationManager.AppSettings["OutboxProviders"].ToString();
        }

        public static TraysHandler getInstance()
        {
            if (_instance == null)
                _instance = new TraysHandler();
            return _instance;
        }


        #region Inbox management

        public string getInboxesFolder()
        {
            return inboxesFolder;
        }

        public void setInboxFolder(string folder)
        {
            inboxesFolder = folder;
        }

        public void scanForInboxes()
        {
            try
            {
                var files = Directory.GetFiles(inboxesFolder).Where(name => name.EndsWith(".dll"));
                foreach (string file in files)
                {
                    Assembly concreteInbox = Assembly.LoadFile(file);
                    //Buscar bandejas de entrada
                    Type t = validateAssembly(concreteInbox, "readMessage");
                    if (t != null)
                    {

                        IInbox newInbox = (IInbox)Activator.CreateInstance(t);
                        //Solo cargo tipos de bandejas que no esten cargados
                        if (inboxProviders.Find(inboxType => inboxType.GetType() == newInbox.GetType()) == null)
                            inboxProviders.Add(newInbox);
                    }


                }
            }
            catch(Exception ex)
            {
                auditLog.Error("Ocurrió un error al tratar de cargar las librerias de las bandejas. El error es: " + ex.Message);
                
            }
            
           
        }

        public IInbox createNewInboxInstance(IInbox inbox)
        {
            return (IInbox)Activator.CreateInstance(inbox.GetType());
        }

        public List<IInbox> getInboxProviders()
        {
            return inboxProviders;
        }

        public List<IOutbox> getOutboxProviders()
        {
            return outboxProviders;
        }

        public void addConcreteInbox(IInbox inbox)
        {
            concreteInboxes.Add(inbox);
        }

        public List<IInbox> getConcreteInboxes()
        {
            return concreteInboxes;
        }

        public void dbSaveConcreteInbox(IInbox inbox)
        {
            using (BizzCommContext ctx = new BizzCommContext())
            {
                TrayEntity tray = new TrayEntity();
                tray.TrayType = "Inbox";
                tray.TypeClass = inbox.GetType().ToString();
                tray.Attributes = inbox.codeMe();
                ctx.Trays.Add(tray);
                ctx.SaveChanges();
            }

        }

        public void dbUpdateConcreteInbox(string inboxAttributes, IInbox updatedInbox)
        {
            
            using (BizzCommContext ctx = new BizzCommContext())
            {
                var tray = (from t in ctx.Trays
                            where t.Attributes == inboxAttributes
                            select t).Single();
                tray.Attributes = updatedInbox.codeMe();
                ctx.SaveChanges();
            }
        }

        public List<IInbox> restoreConcreteInboxesFromDB()
        {
            List<IInbox> ret = new List<IInbox>();
            using (BizzCommContext ctx = new BizzCommContext())
            {
                var trays = from t in ctx.Trays
                            where t.TrayType == "inbox"
                            select t;
                foreach (TrayEntity tray in trays)
                {

                    Type type = findClassType(tray.TypeClass,"inbox");
                    IInbox newInbox = (IInbox)Activator.CreateInstance(type);
                    newInbox.decodeMe(tray.Attributes);
                    concreteInboxes.Add(newInbox);
                }

            }
            return ret;

        }

        public void removeConcreteInbox(IInbox inbox)
        {
            concreteInboxes.Remove(inbox);
            TrayEntity tray = getTrayFromInBox(inbox);
            removeTrayEnityFromDb(tray);

        }

        public TrayEntity getTrayFromInBox(IInbox inbox)
        {
            string inBoxAttributes = inbox.codeMe();
            using (BizzCommContext ctx = new BizzCommContext())
            {
                var tray = (from t in ctx.Trays
                            where t.Attributes == inBoxAttributes
                            select t).Single();
                return tray;
            }
        }


        #endregion

        #region Outbox management

        public string getOutboxesFolder()
        {
            return outboxesFolder;
        }

        public void setOutboxFolder(string folder)
        {
            outboxesFolder = folder;
        }

        public void scanForOutboxes()
        {
            try
            {
                var files = Directory.GetFiles(inboxesFolder).Where(name => name.EndsWith(".dll"));
                foreach (string file in files)
                {
                    Assembly concreteOutbox = Assembly.LoadFile(file);
                    //Buscar bandejas de salida 

                    Type t = validateAssembly(concreteOutbox, "writeMessage");
                    if (t != null)
                    {
                        IOutbox newOutbox = (IOutbox)Activator.CreateInstance(t);
                        //Solo cargo tipos de bandejas que no esten cargados
                        if (outboxProviders.Find(outboxType => outboxType.GetType() == newOutbox.GetType()) == null)
                            outboxProviders.Add(newOutbox);
                    }
                }
            }
            catch (Exception ex)
            {
                auditLog.Error("Ocurrió un error al tratar de cargar las librerias de las bandejas. El error es: " + ex.Message);
            }

        }

        public IOutbox createNewOutboxInstance(IOutbox outbox)
        {
            return (IOutbox)Activator.CreateInstance(outbox.GetType());
        }
        
        public void addConcreteOutbox(IOutbox outbox)
        {
            concreteOutboxes.Add(outbox);
        }

        public List<IOutbox> getConcreteOutbox()
        {
            return concreteOutboxes;
        }

        public void dbSaveConcreteOutbox(IOutbox outbox)
        {
            using (BizzCommContext ctx = new BizzCommContext())
            {
                TrayEntity tray = new TrayEntity();
                tray.TrayType = "Outbox";
                tray.TypeClass = outbox.GetType().ToString();
                tray.Attributes = outbox.codeMe();
                ctx.Trays.Add(tray);
                ctx.SaveChanges();
            }

        }


        public List<IOutbox> restoreConcreteOutboxesFromDB()
        {
            List<IOutbox> ret = new List<IOutbox>();
            using (BizzCommContext ctx = new BizzCommContext())
            {
                var trays = from t in ctx.Trays
                            where t.TrayType == "Outbox"
                            select t;
                foreach (TrayEntity tray in trays)
                {

                    Type type = findClassType(tray.TypeClass, "outbox");
                    IOutbox newOutbox = (IOutbox)Activator.CreateInstance(type);
                    newOutbox.decodeMe(tray.Attributes);
                    concreteOutboxes.Add(newOutbox);
                }

            }
            return ret;

        }


        public IOutbox getOutboxFromTrayEntity(TrayEntity tray)
        {
            IOutbox retOutbox = null;
            foreach (IOutbox outbox in concreteOutboxes)
            {
                if (outbox.codeMe() == tray.Attributes)
                    retOutbox = outbox;
            }
            return retOutbox;
        }



        public TrayEntity getTrayFromOutBox(IOutbox outbox)
        {
            string outBoxAttributes = outbox.codeMe();
            using (BizzCommContext ctx = new BizzCommContext())
            {
                var tray = (from t in ctx.Trays
                            where t.Attributes == outBoxAttributes
                            select t).Single();
                return tray;
            }
        }

        public void dbUpdateConcreteOutbox(string OutboxAttributes, IOutbox updatedOutbox)
        {

            using (BizzCommContext ctx = new BizzCommContext())
            {
                var tray = (from t in ctx.Trays
                            where t.Attributes == OutboxAttributes
                            select t).Single();
                tray.Attributes = updatedOutbox.codeMe();
                ctx.SaveChanges();
            }
        }


        public void removeConcreteOutbox(IOutbox outbox)
        {
            if (!RulesHandler.getInstance().isOutBoxInUse(outbox))
            {
                concreteOutboxes.Remove(outbox);
                TrayEntity tray = getTrayFromOutBox(outbox);
                removeTrayEnityFromDb(tray);
            }
            else
                throw new Exception("La bandeja esta en uso por una regla");

        }


        #endregion

        #region Common methods

        public void removeTrayEnityFromDb(TrayEntity tray)
        {
            using (BizzCommContext ctx = new BizzCommContext())
            {
                ctx.Trays.Attach(tray);
                ctx.Trays.Remove(tray);
                ctx.SaveChanges();
            }
        }


        private Type validateAssembly(Assembly assembly, string operation)
        {
            Type ret = null;

            Type[] types = assembly.GetTypes();

            foreach (Type t in types)
            {

                var method = t.GetMethods().Where(met => met.Name.Contains(operation)).FirstOrDefault();
                if (method != null)
                    ret = t;
            }


            return ret;
        }

        private Type findClassType(string strType, string trayType)
        {
            Type typeReturn = null;
            switch (trayType)
            {
                case "inbox":
                    {
                        var type = (from t in inboxProviders
                                    where t.GetType().ToString() == strType
                                    select t).FirstOrDefault();
                        typeReturn = type.GetType();
                    }break;
                case "outbox":
                    {
                        var type = (from t in outboxProviders
                                    where t.GetType().ToString() == strType
                                    select t).FirstOrDefault();
                        typeReturn = type.GetType();
                    }break;
            }
            return typeReturn;
            
            
        }

        #endregion

    }
}
